import axios, { AxiosInstance, AxiosResponse } from 'axios';
import { RootState, store } from '../actions/store';

/**
 * Request to the backend.
 * @example { "version": 1, "request": {...} }
 */
interface NulRequest<T> {
  /**
   * Protocol version.
   */
  version: number;
  /**
   * Request body.
   */
  request: T;
};

/**
 * Response from the backend.
 * @example { "version": 1, "code": 0, "message": "", "result": { ... } }
 */
interface NulResponse<T> {
  /**
   * Protocol version.
   */
  version: number;
  /**
   * Error code.
   * Note: Set this variable to 0 if the operation succeeded.
   */
  code: number;
  /**
   * Error message.
   * Note: Set this variable to "" if the operation succeeded, not null.
   */
  message: string;
  /**
   * Result body.
   * Note: Set this variable to null if no response body to handle.
   */
  result: T;
};

/**
 * Create a standard request with given request body.
 * @param request request body
 * @returns a wrapped standard request object to send
 */
function createRequest<T>(request: T): NulRequest<T> {
  return {
    version: 1,
    request
  };
}

/**
 * Create an empty standard request.
 * @returns an empty standard request object to send
 */
function createEmptyRequest(): NulRequest<null> {
  return {
    version: 1,
    request: null
  };
}

/**
 * Send a request to the backend using POST protocol.
 * Requests and responses are automatically wrapped.
 * @param url path to the backend interface
 * @param request request body, default to undefined
 * @param withToken send the request with token, default to true
 * @param T type of request body, default to null
 * @param R type of response body, default to null
 * @returns a promise object with response sent by axios
 */
async function request<R = null, T = null>(url: string, request?: T, withToken: boolean = true): Promise<AxiosResponse<NulResponse<R>, any>> {
  const state: RootState = store.getState();
  const instance: AxiosInstance = axios.create({
    baseURL: 'http://localhost:8000'
  });
  if (withToken) {
    if (!state.Auth.loggedIn) {
      throw new Error('Not logged in.');
    }
    instance.interceptors.request.use((config) => {
      config.headers.Authorization = `Bearer ${state.Auth.token}`;
      return config;
    });
  }
  let result: Promise<AxiosResponse<NulResponse<R>, any>>;
  if (request === undefined) {
    result = instance.post<NulResponse<R>, AxiosResponse<NulResponse<R>, any>, NulRequest<null>>(url, createEmptyRequest());
  }
  else {
    result = instance.post<NulResponse<R>, AxiosResponse<NulResponse<R>, any>, NulRequest<T>>(url, createRequest(request));
  }
  return result;
}

export { request, createRequest, createEmptyRequest };
export type { NulRequest, NulResponse };
